﻿// PolyGraph3D
//
// A 3D runtime engine for Silverlight 1.0

$Xaml = function(elementName, additionalAttributes, content)
{
    if (additionalAttributes == null)
        additionalAttributes = "";
    if (content == null)
        content = "";
    return "<" + elementName + " xmlns=\"http://schemas.microsoft.com/client/2007\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" " + additionalAttributes + " >" + content + "</" + elementName + ">";
}
$CanvasXaml = function(zIndex)
{
    return $Xaml("Canvas", "Canvas.ZIndex=\"" + zIndex.toString() + "\"");
}
$PathXaml = function(bgColor, data, zIndex)
{
    return $Xaml("Path", "Fill=\"" + bgColor + "\" Data=\"" + data + "\" Canvas.ZIndex=\"" + zIndex.toString() + "\" Stroke=\"White\"");
}

if (!window.PolyGraph3D)
    PolyGraph3D = {};

/// Region: 2D Vectors

PolyGraph3D.Vector2D = function(x, y)
{
    this.X = new Number(x);
    this.Y = new Number(y);
}

PolyGraph3D.Vector2D.prototype.Length = function()
{
     var num = (this.X * this.X) + (this.Y * this.Y);
     return Math.sqrt(num);
}

PolyGraph3D.Vector2D.prototype.Copy = function()
{
     return new PolyGraph3D.Vector2D(this.X, this.Y);
}

PolyGraph3D.Vector2D.prototype.IsEqual = function(compareTo)
{
    if (this.X != compareTo.X)
        return false;
    if (this.Y != compareTo.Y)
        return false;
    return true;
}

/// Standard Vectors
PolyGraph3D.Vector2D.One = new PolyGraph3D.Vector2D(1.0, 1.0);
PolyGraph3D.Vector2D.X = new PolyGraph3D.Vector2D(1.0, 0.0);
PolyGraph3D.Vector2D.Y = new PolyGraph3D.Vector2D(0.0, 1.0);
PolyGraph3D.Vector2D.Zero = new PolyGraph3D.Vector2D(0.0, 0.0);

/// Static methods
PolyGraph3D.Vector2D.DotProduct = function(vector1, vector2)
{
    return (vector1.X * vector2.X) + (vector1.Y * vector2.Y);
}

PolyGraph3D.Vector2D.DistanceBetween = function(vector1, vector2)
{
    var x = vector1.X - vector2.X;
    var y = vector1.Y - vector2.Y;
    var num3 = (x * x) + (y * y);
    return Math.sqrt(num3);
}

PolyGraph3D.Vector2D.Normalize = function(vector)
{
    var num = 1.0 / vector.Length();
    vector.X = vector.X * num;
    vector.Y = vector.Y * num;
}

PolyGraph3D.Vector2D.Min = function(vector1, vector2)
{
    var vector3 = new PolyGraph3D.Vector2D(0,0);
    vector3.X = (vector1.X < vector2.X) ? vector1.X : vector2.X;
    vector3.Y = (vector1.Y < vector2.Y) ? vector1.Y : vector2.Y;
    return vector3;
}

PolyGraph3D.Vector2D.Max = function(vector1, vector2)
{
    var vector3 = new PolyGraph3D.Vector2D(0,0);
    vector3.X = (vector1.X > vector2.X) ? vector1.X : vector2.X;
    vector3.Y = (vector1.Y > vector2.Y) ? vector1.Y : vector2.Y;
    return vector3;
}

PolyGraph3D.Vector2D.Add = function(vector1, vector2)
{
    return new PolyGraph3D.Vector2D(vector1.X + vector2.X, vector1.Y + vector2.Y);
}

PolyGraph3D.Vector2D.Subtract = function(vector1, vector2)
{
    return new PolyGraph3D.Vector2D(vector1.X - vector2.X, vector1.Y - vector2.Y);
}

PolyGraph3D.Vector2D.Multiply = function(vector1, vector2)
{
    return new PolyGraph3D.Vector2D(vector1.X * vector2.X, vector1.Y * vector2.Y);
}

PolyGraph3D.Vector2D.Divide = function(vector1, vector2)
{
    return new PolyGraph3D.Vector2D(vector1.X / vector2.X, vector1.Y / vector2.Y);
}

PolyGraph3D.Vector2D.Negate = function(vector)
{
    return new PolyGraph3D.Vector2D(-vector.X, -vector.Y);
}

PolyGraph3D.Vector2D.Transform = function(positionVector, matrix)
{
    var x = ((positionVector.X * matrix.M1_1) + (positionVector.Y * matrix.M2_1)) + matrix.M4_1;
    var y = ((positionVector.X * matrix.M1_2) + (positionVector.Y * matrix.M2_2)) + matrix.M4_2;
    return new PolyGraph3D.Vector2D(x, y);
}

PolyGraph3D.Vector2D.TransformNormal = function(normal, matrix)
{
    var x = (normal.X * matrix.M1_1) + (normal.Y * matrix.M2_1);
    var y = (normal.X * matrix.M1_2) + (normal.Y * matrix.M2_2);
    return new PolyGraph3D.Vector2D(x, y);
}
/// EndRegion: 2D Vectors



/// Region: 3D Vectors

PolyGraph3D.Vector = function(x, y, z)
{
    this.X = new Number(x);
    this.Y = new Number(y);
    this.Z = new Number(z);
}
PolyGraph3D.Point = PolyGraph3D.Vector;

PolyGraph3D.Vector.prototype.Length = function()
{
    var num = (this.X * this.X) + (this.Y * this.Y) + (this.Z * this.Z);
    return Math.sqrt(num);
}

PolyGraph3D.Vector.prototype.Copy = function()
{
    return new PolyGraph3D.Vector(this.X, this.Y, this.Z);
}

PolyGraph3D.Vector.prototype.IsEqual = function(compareTo)
{
    if (this.X != compareTo.X)
        return false;
    if (this.Y != compareTo.Y)
        return false;
    if (this.Z != compareTo.Z)
        return false;
    return true;
}

PolyGraph3D.Vector.prototype.ApplyTransform = function(matrix)
{
    var x = (this.X * matrix._M1_1) + (this.Y * matrix._M2_1) + (this.Z * matrix._M3_1) + matrix._M4_1;
    var y = (this.X * matrix._M1_2) + (this.Y * matrix._M2_2) + (this.Z * matrix._M3_2) + matrix._M4_2;
    var z = (this.X * matrix._M1_3) + (this.Y * matrix._M2_3) + (this.Z * matrix._M3_3) + matrix._M4_3;
    this.X = x;
    this.Y = y;
    this.Z = z;
}

/// Standard Vectors
PolyGraph3D.Vector.One = new PolyGraph3D.Vector(1.0, 1.0, 1.0);
PolyGraph3D.Vector.X = new PolyGraph3D.Vector(1.0, 0.0, 0.0);
PolyGraph3D.Vector.Y = new PolyGraph3D.Vector(0.0, 1.0, 0.0);
PolyGraph3D.Vector.Z = new PolyGraph3D.Vector(0.0, 0.0, 1.0);
PolyGraph3D.Vector.Zero = new PolyGraph3D.Vector(0.0, 0.0, 0.0);

/// Static methods
PolyGraph3D.Vector.Clone = function(vector)
{
    return new PolyGraph3D.Vector(vector.X, vector.Y, vector.Z);
}

PolyGraph3D.Vector.DotProduct = function(vector1, vector2)
{
    return (vector1.X * vector2.X) + (vector1.Y * vector2.Y) + (vector1.Z * vector2.Z);
}

PolyGraph3D.Vector.DistanceBetween = function(vector1, vector2)
{
    var x = vector1.X - vector2.X;
    var y = vector1.Y - vector2.Y;
    var z = vector1.Z - vector2.Z;
    var num3 = (x * x) + (y * y) + (z * z);
    return Math.sqrt(num3);
}

PolyGraph3D.Vector.Normalize = function(vector)
{
    var num = 1.0 / vector.Length();
    vector.X = vector.X * num;
    vector.Y = vector.Y * num;
    vector.Z = vector.Z * num;
}

PolyGraph3D.Vector.Min = function(vector1, vector2)
{
    var vector3 = new PolyGraph3D.Vector(0,0,0);
    vector3.X = (vector1.X < vector2.X) ? vector1.X : vector2.X;
    vector3.Y = (vector1.Y < vector2.Y) ? vector1.Y : vector2.Y;
    vector3.Z = (vector1.Z < vector2.Z) ? vector1.Z : vector2.Z;
    return vector3;
}

PolyGraph3D.Vector.Max = function(vector1, vector2)
{
    var vector3 = new PolyGraph3D.Vector(0,0,0);
    vector3.X = (vector1.X > vector2.X) ? vector1.X : vector2.X;
    vector3.Y = (vector1.Y > vector2.Y) ? vector1.Y : vector2.Y;
    vector3.Z = (vector1.Z > vector2.Z) ? vector1.Z : vector2.Z;
    return vector3;
}

PolyGraph3D.Vector.Add = function(vector1, vector2)
{
    return new PolyGraph3D.Vector(vector1.X + vector2.X, vector1.Y + vector2.Y, vector1.Z + vector2.Z);
}

PolyGraph3D.Vector.Combine = function(vector1, vector2)
{
    vector1.X += vector2.X
    vector1.Y += vector2.Y
    vector1.Z += vector2.Z;
}

PolyGraph3D.Vector.Average = function(vectors)
{
    var result = PolyGraph3D.Vector.Zero.Copy();
    if (vectors.length == 0)
        return result;
    for (var ix = 0; ix < vectors.length; ix++)
        PolyGraph3D.Vector.Combine(result, vectors[ix]);
    result.X = result.X / vectors.length;
    result.Y = result.Y / vectors.length;
    result.Z = result.Z / vectors.length;
    return result;
}

PolyGraph3D.Vector.Subtract = function(vector1, vector2)
{
    return new PolyGraph3D.Vector(vector1.X - vector2.X, vector1.Y - vector2.Y, vector1.Z - vector2.Z);
}

PolyGraph3D.Vector.Multiply = function(vector1, vector2)
{
    return new PolyGraph3D.Vector(vector1.X * vector2.X, vector1.Y * vector2.Y, vector1.Z * vector2.Z);
}

PolyGraph3D.Vector.Scale = function(vector, scale)
{
    return new PolyGraph3D.Vector(vector.X * scale, vector.Y * scale, vector.Z * scale);
}

PolyGraph3D.Vector.Divide = function(vector1, vector2)
{
    return new PolyGraph3D.Vector(vector1.X / vector2.X, vector1.Y / vector2.Y, vector1.Z / vector2.Z);
}

PolyGraph3D.Vector.Negate = function(vector)
{
    return new PolyGraph3D.Vector(-vector.X, -vector.Y, -vector.Z);
}

PolyGraph3D.Vector.CrossProduct = function(vector1, vector2)
{
    // (a2b3 − a3b2, a3b1 − a1b3, a1b2 − a2b1)
    var x = (vector1.Y * vector2.Z) - (vector1.Z * vector2.Y);
    var y = (vector1.Z * vector2.X) - (vector1.X * vector2.Z);
    var z = (vector1.X * vector2.Y) - (vector1.Y * vector2.X);
    return new PolyGraph3D.Vector(x, y, z);
}

PolyGraph3D.Vector.FaceNormal = function(vector1, vector2)
{
    var xProd = PolyGraph3D.Vector.CrossProduct(vector1, vector2);
    return PolyGraph3D.Vector.Normalize(xProd);
}

PolyGraph3D.Vector.Transform = function(vector, matrix)
{
    var x = (vector.X * matrix._M1_1) + (vector.Y * matrix._M2_1) + (vector.Z * matrix._M3_1) + matrix._M4_1;
    var y = (vector.X * matrix._M1_2) + (vector.Y * matrix._M2_2) + (vector.Z * matrix._M3_2) + matrix._M4_2;
    var z = (vector.X * matrix._M1_3) + (vector.Y * matrix._M2_3) + (vector.Z * matrix._M3_3) + matrix._M4_3;
    return new PolyGraph3D.Vector(x, y, z);
}
/// EndRegion: 3D Vectors


/// Region: Transformation Matrices
PolyGraph3D.Matrix = function()
{
    this._M1_1 = 0; this._M1_2 = 0; this._M1_3 = 0; this._M1_4 = 0;
    this._M2_1 = 0; this._M2_2 = 0; this._M2_3 = 0; this._M2_4 = 0;
    this._M3_1 = 0; this._M3_2 = 0; this._M3_3 = 0; this._M3_4 = 0;
    this._M4_1 = 0; this._M4_2 = 0; this._M4_3 = 0; this._M4_4 = 0;
}

PolyGraph3D.Matrix.InitializeNew = function(
    m1_1, m1_2, m1_3, m1_4,
    m2_1, m2_2, m2_3, m2_4,
    m3_1, m3_2, m3_3, m3_4,
    m4_1, m4_2, m4_3, m4_4)
{
    var mx = new PolyGraph3D.Matrix();
    mx._M1_1 = m1_1; mx._M1_2 = m1_2; mx._M1_3 = m1_3; mx._M1_4 = m1_4;
    mx._M2_1 = m2_1; mx._M2_2 = m2_2; mx._M2_3 = m2_3; mx._M2_4 = m2_4;
    mx._M3_1 = m3_1; mx._M3_2 = m3_2; mx._M3_3 = m3_3; mx._M3_4 = m3_4;
    mx._M4_1 = m4_1; mx._M4_2 = m4_2; mx._M4_3 = m4_3; mx._M4_4 = m4_4;
    return mx;
}

PolyGraph3D.Matrix.IdentityMatrix = PolyGraph3D.Matrix.InitializeNew(
    1, 0, 0, 0,
    0, 1, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1);

PolyGraph3D.Matrix.CreateRotationX = function(radians)
{
    var matrix = new PolyGraph3D.Matrix();
    var sinT = Math.sin(radians);
    var cosT = Math.cos(radians);
    matrix._M1_1 = 1; matrix._M1_2 = 0;     matrix._M1_3 = 0;    matrix._M1_4 = 0;
    matrix._M2_1 = 0; matrix._M2_2 = cosT;  matrix._M2_3 = sinT; matrix._M2_4 = 0;
    matrix._M3_1 = 0; matrix._M3_2 = -sinT; matrix._M3_3 = cosT; matrix._M3_4 = 0;
    matrix._M4_1 = 0; matrix._M4_2 = 0;     matrix._M4_3 = 0;    matrix._M4_4 = 1;
    return matrix;
}

PolyGraph3D.Matrix.CreateRotationY = function(radians)
{
    var matrix = new PolyGraph3D.Matrix();
    var sinT = Math.sin(radians);
    var cosT = Math.cos(radians);
    matrix._M1_1 = cosT; matrix._M1_2 = 0; matrix._M1_3 = -sinT; matrix._M1_4 = 0;
    matrix._M2_1 = 0;    matrix._M2_2 = 1; matrix._M2_3 = 0;     matrix._M2_4 = 0;
    matrix._M3_1 = sinT; matrix._M3_2 = 0; matrix._M3_3 = cosT;  matrix._M3_4 = 0;
    matrix._M4_1 = 0;    matrix._M4_2 = 0; matrix._M4_3 = 0;     matrix._M4_4 = 1;
    return matrix;
}

PolyGraph3D.Matrix.CreateRotationZ = function(radians)
{
    var matrix = new PolyGraph3D.Matrix();
    var sinT = Math.sin(radians);
    var cosT = Math.cos(radians);
    matrix._M1_1 = cosT;  matrix._M1_2 = sinT; matrix._M1_3 = 0; matrix._M1_4 = 0;
    matrix._M2_1 = -sinT; matrix._M2_2 = cosT; matrix._M2_3 = 0; matrix._M2_4 = 0;
    matrix._M3_1 = 0;     matrix._M3_2 = 0;    matrix._M3_3 = 1; matrix._M3_4 = 0;
    matrix._M4_1 = 0;     matrix._M4_2 = 0;    matrix._M4_3 = 0; matrix._M4_4 = 1;
    return matrix;
}

PolyGraph3D.Matrix.CreateRotation = function(vector)
{
    var mxX = PolyGraph3D.Matrix.CreateRotationX(vector.X);
    var mxY = PolyGraph3D.Matrix.CreateRotationY(vector.Y);
    var mxZ = PolyGraph3D.Matrix.CreateRotationZ(vector.Z);
    var mxTemp = PolyGraph3D.Matrix.Multiply(mxX, mxY);
    return PolyGraph3D.Matrix.Multiply(mxTemp, mxZ);
}

PolyGraph3D.Matrix.Invert = function(matrix)
{
    var result = new PolyGraph3D.Matrix();
    var num5 = matrix._M1_1;
    var num4 = matrix._M1_2;
    var num3 = matrix._M1_3;
    var num2 = matrix._M1_4;
    var num9 = matrix._M2_1;
    var num8 = matrix._M2_2;
    var num7 = matrix._M2_3;
    var num6 = matrix._M2_4;
    var num17 = matrix._M3_1;
    var num16 = matrix._M3_2;
    var num15 = matrix._M3_3;
    var num14 = matrix._M3_4;
    var num13 = matrix._M4_1;
    var num12 = matrix._M4_2;
    var num11 = matrix._M4_3;
    var num10 = matrix._M4_4;
    var num23 = (num15 * num10) - (num14 * num11);
    var num22 = (num16 * num10) - (num14 * num12);
    var num21 = (num16 * num11) - (num15 * num12);
    var num20 = (num17 * num10) - (num14 * num13);
    var num19 = (num17 * num11) - (num15 * num13);
    var num18 = (num17 * num12) - (num16 * num13);
    var num39 = ((num8 * num23) - (num7 * num22)) + (num6 * num21);
    var num38 = -(((num9 * num23) - (num7 * num20)) + (num6 * num19));
    var num37 = ((num9 * num22) - (num8 * num20)) + (num6 * num18);
    var num36 = -(((num9 * num21) - (num8 * num19)) + (num7 * num18));
    var num = 1 / ((((num5 * num39) + (num4 * num38)) + (num3 * num37)) + (num2 * num36));
    result._M1_1 = num39 * num;
    result._M2_1 = num38 * num;
    result._M3_1 = num37 * num;
    result._M4_1 = num36 * num;
    result._M1_2 = -(((num4 * num23) - (num3 * num22)) + (num2 * num21)) * num;
    result._M2_2 = (((num5 * num23) - (num3 * num20)) + (num2 * num19)) * num;
    result._M3_2 = -(((num5 * num22) - (num4 * num20)) + (num2 * num18)) * num;
    result._M4_2 = (((num5 * num21) - (num4 * num19)) + (num3 * num18)) * num;
    var num35 = (num7 * num10) - (num6 * num11);
    var num34 = (num8 * num10) - (num6 * num12);
    var num33 = (num8 * num11) - (num7 * num12);
    var num32 = (num9 * num10) - (num6 * num13);
    var num31 = (num9 * num11) - (num7 * num13);
    var num30 = (num9 * num12) - (num8 * num13);
    result._M1_3 = (((num4 * num35) - (num3 * num34)) + (num2 * num33)) * num;
    result._M2_3 = -(((num5 * num35) - (num3 * num32)) + (num2 * num31)) * num;
    result._M3_3 = (((num5 * num34) - (num4 * num32)) + (num2 * num30)) * num;
    result._M4_3 = -(((num5 * num33) - (num4 * num31)) + (num3 * num30)) * num;
    var num29 = (num7 * num14) - (num6 * num15);
    var num28 = (num8 * num14) - (num6 * num16);
    var num27 = (num8 * num15) - (num7 * num16);
    var num26 = (num9 * num14) - (num6 * num17);
    var num25 = (num9 * num15) - (num7 * num17);
    var num24 = (num9 * num16) - (num8 * num17);
    result._M1_4 = -(((num4 * num29) - (num3 * num28)) + (num2 * num27)) * num;
    result._M2_4 = (((num5 * num29) - (num3 * num26)) + (num2 * num25)) * num;
    result._M3_4 = -(((num5 * num28) - (num4 * num26)) + (num2 * num24)) * num;
    result._M4_4 = (((num5 * num27) - (num4 * num25)) + (num3 * num24)) * num;
    return result;
}

PolyGraph3D.Matrix.CreateTranslationMatrix = function(x, y, z)
{
    var result = new PolyGraph3D.Matrix();
    result._M1_1 = 1; result._M1_2 = 0; result._M1_3 = 0; result._M1_4 = 0;
    result._M2_1 = 0; result._M2_2 = 1; result._M2_3 = 0; result._M2_4 = 0;
    result._M3_1 = 0; result._M3_2 = 0; result._M3_3 = 1; result._M3_4 = 0;
    result._M4_1 = x; result._M4_2 = y; result._M4_3 = z; result._M4_4 = 1;
    return result;
}

PolyGraph3D.Matrix.CreateTranslation = function(vector)
{
    return PolyGraph3D.Matrix.CreateTranslationMatrix(vector.X, vector.Y, vector.Z);
}

PolyGraph3D.Matrix.Transpose = function(matrix)
{
    var result = new PolyGraph3D.Matrix();
    result._M1_1 = matrix._M1_1; result._M1_2 = matrix._M2_1; result._M1_3 = matrix._M3_1; result._M1_4 = matrix._M4_1;
    result._M2_1 = matrix._M1_2; result._M2_2 = matrix._M2_2; result._M2_3 = matrix._M3_2; result._M2_4 = matrix._M4_2;
    result._M3_1 = matrix._M1_3; result._M3_2 = matrix._M2_3; result._M3_3 = matrix._M3_3; result._M3_4 = matrix._M4_3;
    result._M4_1 = matrix._M1_4; result._M4_2 = matrix._M2_4; result._M4_3 = matrix._M3_4; result._M4_4 = matrix._M4_4;
    return result;
}

PolyGraph3D.Matrix.Multiply = function(matrix1, matrix2)
{
    var M1_1 = (((matrix1._M1_1 * matrix2._M1_1) + (matrix1._M1_2 * matrix2._M2_1)) + (matrix1._M1_3 * matrix2._M3_1)) + (matrix1._M1_4 * matrix2._M4_1);
    var M1_2 = (((matrix1._M1_1 * matrix2._M1_2) + (matrix1._M1_2 * matrix2._M2_2)) + (matrix1._M1_3 * matrix2._M3_2)) + (matrix1._M1_4 * matrix2._M4_2);
    var M1_3 = (((matrix1._M1_1 * matrix2._M1_3) + (matrix1._M1_2 * matrix2._M2_3)) + (matrix1._M1_3 * matrix2._M3_3)) + (matrix1._M1_4 * matrix2._M4_3);
    var M1_4 = (((matrix1._M1_1 * matrix2._M1_4) + (matrix1._M1_2 * matrix2._M2_4)) + (matrix1._M1_3 * matrix2._M3_4)) + (matrix1._M1_4 * matrix2._M4_4);
    var M2_1 = (((matrix1._M2_1 * matrix2._M1_1) + (matrix1._M2_2 * matrix2._M2_1)) + (matrix1._M2_3 * matrix2._M3_1)) + (matrix1._M2_4 * matrix2._M4_1);
    var M2_2 = (((matrix1._M2_1 * matrix2._M1_2) + (matrix1._M2_2 * matrix2._M2_2)) + (matrix1._M2_3 * matrix2._M3_2)) + (matrix1._M2_4 * matrix2._M4_2);
    var M2_3 = (((matrix1._M2_1 * matrix2._M1_3) + (matrix1._M2_2 * matrix2._M2_3)) + (matrix1._M2_3 * matrix2._M3_3)) + (matrix1._M2_4 * matrix2._M4_3);
    var M2_4 = (((matrix1._M2_1 * matrix2._M1_4) + (matrix1._M2_2 * matrix2._M2_4)) + (matrix1._M2_3 * matrix2._M3_4)) + (matrix1._M2_4 * matrix2._M4_4);
    var M3_1 = (((matrix1._M3_1 * matrix2._M1_1) + (matrix1._M3_2 * matrix2._M2_1)) + (matrix1._M3_3 * matrix2._M3_1)) + (matrix1._M3_4 * matrix2._M4_1);
    var M3_2 = (((matrix1._M3_1 * matrix2._M1_2) + (matrix1._M3_2 * matrix2._M2_2)) + (matrix1._M3_3 * matrix2._M3_2)) + (matrix1._M3_4 * matrix2._M4_2);
    var M3_3 = (((matrix1._M3_1 * matrix2._M1_3) + (matrix1._M3_2 * matrix2._M2_3)) + (matrix1._M3_3 * matrix2._M3_3)) + (matrix1._M3_4 * matrix2._M4_3);
    var M3_4 = (((matrix1._M3_1 * matrix2._M1_4) + (matrix1._M3_2 * matrix2._M2_4)) + (matrix1._M3_3 * matrix2._M3_4)) + (matrix1._M3_4 * matrix2._M4_4);
    var M4_1 = (((matrix1._M4_1 * matrix2._M1_1) + (matrix1._M4_2 * matrix2._M2_1)) + (matrix1._M4_3 * matrix2._M3_1)) + (matrix1._M4_4 * matrix2._M4_1);
    var M4_2 = (((matrix1._M4_1 * matrix2._M1_2) + (matrix1._M4_2 * matrix2._M2_2)) + (matrix1._M4_3 * matrix2._M3_2)) + (matrix1._M4_4 * matrix2._M4_2);
    var M4_3 = (((matrix1._M4_1 * matrix2._M1_3) + (matrix1._M4_2 * matrix2._M2_3)) + (matrix1._M4_3 * matrix2._M3_3)) + (matrix1._M4_4 * matrix2._M4_3);
    var M4_4 = (((matrix1._M4_1 * matrix2._M1_4) + (matrix1._M4_2 * matrix2._M2_4)) + (matrix1._M4_3 * matrix2._M3_4)) + (matrix1._M4_4 * matrix2._M4_4);
    
    return PolyGraph3D.Matrix.InitializeNew(
        M1_1, M1_2, M1_3, M1_4,
        M2_1, M2_2, M2_3, M2_4,
        M3_1, M3_2, M3_3, M3_4,
        M4_1, M4_2, M4_3, M4_4);
}

PolyGraph3D.Matrix.Add = function(matrix1, matrix2)
{
    var result = new PolyGraph3D.Matrix();
    result._M1_1 = matrix1._M1_1 + matrix2._M1_1;
    result._M1_2 = matrix1._M1_2 + matrix2._M1_2;
    result._M1_3 = matrix1._M1_3 + matrix2._M1_3;
    result._M1_4 = matrix1._M1_4 + matrix2._M1_4;
    result._M2_1 = matrix1._M2_1 + matrix2._M2_1;
    result._M2_2 = matrix1._M2_2 + matrix2._M2_2;
    result._M2_3 = matrix1._M2_3 + matrix2._M2_3;
    result._M2_4 = matrix1._M2_4 + matrix2._M2_4;
    result._M3_1 = matrix1._M3_1 + matrix2._M3_1;
    result._M3_2 = matrix1._M3_2 + matrix2._M3_2;
    result._M3_3 = matrix1._M3_3 + matrix2._M3_3;
    result._M3_4 = matrix1._M3_4 + matrix2._M3_4;
    result._M4_1 = matrix1._M4_1 + matrix2._M4_1;
    result._M4_2 = matrix1._M4_2 + matrix2._M4_2;
    result._M4_3 = matrix1._M4_3 + matrix2._M4_3;
    result._M4_4 = matrix1._M4_4 + matrix2._M4_4;
    return result;
}
/// EndRegion: Transformation Matrices


/// Region: Camera
PolyGraph3D.Camera = function()
{
    this._focalLength = 1.0;
    this._location = PolyGraph3D.Vector.Zero;
    this._direction = PolyGraph3D.Vector.Negate(PolyGraph3D.Vector.Z);
    this._rotation = PolyGraph3D.Vector.Zero;
    this._UpdateMx();
}

PolyGraph3D.Camera.prototype.set_FocalLength = function(focalLength)
{
    this._focalLength = focalLength;
    this._UpdateMx();
}

PolyGraph3D.Camera.prototype.get_FocalLength = function()
{
    return this._focalLength;
}

PolyGraph3D.Camera.prototype.set_Location = function(locationVector)
{
    this._location = locationVector;
    this._UpdateMx();
}

PolyGraph3D.Camera.prototype.get_Location = function()
{
    return PolyGraph3D.Vector.Clone(this._location);
}

PolyGraph3D.Camera.prototype.set_Direction = function(directionVector)
{
    this._direction = directionVector;
    this._UpdateMx();
}

PolyGraph3D.Camera.prototype.get_Direction = function()
{
    return PolyGraph3D.Vector.Clone(this._direction);
}

PolyGraph3D.Camera.prototype.set_Rotation = function(rotationVector)
{
    this._rotation = rotationVector;
    this._UpdateMx();
}

PolyGraph3D.Camera.prototype.get_Rotation = function()
{
    return PolyGraph3D.Vector.Clone(this._rotation);
}

PolyGraph3D.Camera.prototype._UpdateMx = function()
{
    // first, normalize the direction
    PolyGraph3D.Vector.Normalize(this._direction);
    
    // calculate the transform matrix to move a point from World coords into Camera coords
    var cameraLoc = PolyGraph3D.Matrix.CreateTranslation(PolyGraph3D.Vector.Negate(this._location));
    var cameraRotation = PolyGraph3D.Matrix.CreateRotation(this._rotation);
    this.cameraTxMx = PolyGraph3D.Matrix.Multiply(cameraLoc, cameraRotation);
    
    // calculate the eye position
    this._eye = PolyGraph3D.Vector.Subtract(this._location,
        PolyGraph3D.Vector.Scale(PolyGraph3D.Vector.Negate(this._direction), this._focalLength));
}

PolyGraph3D.Camera.prototype.OrientToCamera = function(vectors)
{
    for (var ix = 0; ix < vectors.length; ix++)
        vectors[ix].ApplyTransform(this.cameraTxMx);
}

PolyGraph3D.Camera.prototype.ProjectWorldToScreen = function(vectors)
{
    var result = new Array();
    for (var ix = 0; ix < vectors.length; ix++)
    {
        var vector = vectors[ix];
        if (vector.Z == 0)
            vector.Z = 0.0001; // prevent DivByZero
        var perspectiveCorrection = this._eye.Z / vector.Z;
        var x = (vector.X - this._eye.X) * perspectiveCorrection;
        var y = (vector.Y - this._eye.Y) * perspectiveCorrection;
        result.push(new PolyGraph3D.Vector2D(x, y));
    }
    return result;
}

PolyGraph3D.Camera.CreateDefaultCamera = function()
{
    var camera = new PolyGraph3D.Camera();

    camera.set_FocalLength(2.0);
    camera.set_Location(PolyGraph3D.Vector.Z);

    return camera;
}
/// EndRegion: Camera



/// Region: ViewPort
PolyGraph3D.ViewPort = function(silverLightHost, xamlContainer)
{
    this._host = silverLightHost;
    this._xaml = xamlContainer;
    this._visuals = new Array();
    this.ViewCamera = new PolyGraph3D.Camera();
    this.UpdateView();
}

PolyGraph3D.ViewPort.prototype.AddVisual = function(visual)
{
    this._visuals.push(visual);
    visual._parent = this;
    this.UpdateView();
}

PolyGraph3D.ViewPort.prototype.RemoveVisual = function(visual)
{
    visual._parent = null;
    this._visuals.remove(visual);
    this.UpdateView();
}

PolyGraph3D.ViewPort._ApplyTransforms = function(rootVisual, points)
{
    var translate = PolyGraph3D.Matrix.CreateTranslation(rootVisual._location);
    var rotation = PolyGraph3D.Matrix.CreateRotation(rootVisual._rotation);
    //rootVisual._scale
    var applied = PolyGraph3D.Matrix.Multiply(translate, rotation);
    for (var ix = 0; ix < points.length; ix++)
        points[ix].ApplyTransform(applied);
    if (rootVisual._parent != null)
        if (rootVisual._parent.constructor == PolyGraph3D.ViewPort)
        {
            rootVisual._parent.ViewCamera.OrientToCamera(points);
        }
        else
        {
            PolyGraph3D.ViewPort._ApplyTransforms(rootVisual._parent, points);
        }
}

PolyGraph3D.ViewPort._MergeUp = function(rootVisual, allFaces)
{
    if (rootVisual != null)
    {
        if (rootVisual.IsContainer == true)
        {
            for (var childIx = 0; childIx < rootVisual._visuals.length; childIx++)
            {
                PolyGraph3D.ViewPort._MergeUp(rootVisual._visuals[childIx], allFaces);
            }
        }
        else
        {
            var points = new Array();
            for (var ix = 0; ix < rootVisual._points.length; ix++)
                points.push(rootVisual._points[ix].Copy());
            PolyGraph3D.ViewPort._ApplyTransforms(rootVisual, points);
            
            var faces = rootVisual._faces;
            for (faceIx = 0; faceIx < faces.length; faceIx++)
            {
                faces[faceIx].set_WorldPoints(points);
                allFaces.push(faces[faceIx]);
            }
        }
    }
}

PolyGraph3D.ViewPort.prototype.UpdateView = function()
{
    // update the view (render all polygons to screen)
    var dummy = "";
    
    // get a list of faces and vertices (in world coordinates)
    var allFaces = new Array();
    for (var visualIx = 0; visualIx < this._visuals.length; visualIx++)
    {
        PolyGraph3D.ViewPort._MergeUp(this._visuals[visualIx], allFaces);
    }
    
    var faceIx;
    var face;
    for (faceIx = 0; faceIx < allFaces.length; faceIx++)
    {
        // perform backface culling
        face = allFaces[faceIx];
        var distanceToFaceCenter = PolyGraph3D.Vector.DistanceBetween(this.ViewCamera._location,
            face.centerOfGravity);
        var cull = (distanceToFaceCenter < 0); // behind the camera
        
        if (!cull)
        {
            var normal = PolyGraph3D.Model.Face.FindCameraNormal(face);
            var pointInFront = PolyGraph3D.Vector.Add(face.pathPoints[1], normal);
            var distanceToFaceOrigin = PolyGraph3D.Vector.DistanceBetween(this.ViewCamera._location,
                face.pathPoints[1]);
            var distanceToPointInFront = PolyGraph3D.Vector.DistanceBetween(this.ViewCamera._location,
                pointInFront);
            cull = (distanceToPointInFront > distanceToFaceOrigin); // looking at the back side
        }

        var screenPoints;

        if (!cull)
        {
            // project to screen
            screenPoints = this.ViewCamera.ProjectWorldToScreen(face.pathPoints);
            
            // perform clipping
            var inView = false;
            for (var pt = 0; pt < screenPoints.length; pt++)
            {
                var screenPt = screenPoints[pt];
                
                if (((screenPt.X > 0) && (screenPt.X < this._xaml.Width)) &&
                    ((screenPt.Y > 0) && (screenPt.Y < this._xaml.Height)))
                {
                    inView = true;
                    break;
                }
            }
            //cull = !inView;
        }
        
        face.set_Visible(!cull);
        
        if (!cull)
        {
            // update the distance to face
            face.set_Distance(distanceToFaceCenter);
            
            // update the screen points
            face.set_ScreenPoints(screenPoints);
        }
    }
    
    // sort for zIndex
    allFaces = allFaces.sort(PolyGraph3D.Model.Face.DistanceComparer);

    // lastly, update the xaml
    for (faceIx = 0; faceIx < allFaces.length; faceIx++)
    {
        face = allFaces[faceIx];
        face.zIndex = faceIx;
        face.Draw(this._host, this._xaml);
    }
}
/// EndRegion: ViewPort



PolyGraph3D.Model = {};

/// Region: Face
PolyGraph3D.Model.Face = function(pointIndices, tex)
{
    if (pointIndices.length < 3)
        throw "At least 3 points needed to define a Face";

    this.EdgeAIx = pointIndices[0];
    this.OriginIx = pointIndices[1];
    this.EdgeBIx = pointIndices[2];
    this.PointIndices = pointIndices;
    this.TextureIx = tex;
    this.OwnerVisual = null;
    
    // render flags
    this.xaml = null;
    this.dirty = true;
    this.pathPoints = new Array(); // only contains the points actually in use by this face
    this.screenPoints = new Array(); // actual canvas coords
    this.zIndex = 0;
    this.visible = true;
    this.centerOfGravity = PolyGraph3D.Vector.Zero.Copy();
    this.distance = 0;
}

PolyGraph3D.Model.Face.DistanceComparer = function(face1, face2)
{
    if (face1.distance < face2.distance)
        return -1;
    if (face1.distance > face2.distance)
        return 1;
    return 0;
}

PolyGraph3D.Model.Face.prototype.Draw = function(host, xamlParent)
{
    var dummy = "";
    if (this.visible)
    {
        if (this.xaml != null)
        {
            xamlParent.Children.Remove(this.xaml);
            this.xaml = null;
        }
        
        var offsetX = xamlParent.Width / 2; 
        var offsetY = xamlParent.Height / 2; 

        var tex = this.OwnerVisual.GetTexture(this.TextureIx);
        var pathData = "M " + (this.screenPoints[0].X + offsetX).toString() + " " + (this.screenPoints[0].Y + offsetY).toString() + " L ";
        for (var pathPt = 1; pathPt < this.screenPoints.length; pathPt++)
        {
            pathData += (this.screenPoints[pathPt].X + offsetX).toString() + " " + (this.screenPoints[pathPt].Y + offsetY).toString() + " ";
        }
        pathData += "Z";
        if (tex.TextureType == "COLOR")
            this.xaml = host.content.CreateFromXAML($PathXaml(tex.Value, pathData, this.zIndex));
        else
            this.xaml = host.content.CreateFromXAML($PathXaml("#000000FF", pathData, this.zIndex)); // images not supported yet
        
        xamlParent.Children.Add(this.xaml);
    }
    else
    {
        if (this.xaml != null)
        {
            xamlParent.Children.Remove(this.xaml);
            this.xaml = null;
        }
    }
    this.dirty = false;
}

PolyGraph3D.Model.Face.FindNormal = function(visual, face)
{
    if ((face.OriginIx < 0) || (face.EdgeAIx < 0) || (face.EdgeBIx < 0))
        return PolyGraph3D.Vector.Z.Copy();
    var origin = visual._points[face.OriginIx];
    var edgeA = visual._points[face.EdgeAIx];
    var edgeB = visual._points[face.EdgeBIx];
    var vectorA = PolyGraph3D.Vector.Subtract(edgeA, origin);
    var vectorB = PolyGraph3D.Vector.Subtract(edgeB, origin);
    var normal = PolyGraph3D.Vector.CrossProduct(vectorA, vectorB);
    PolyGraph3D.Vector.Normalize(normal);
    return normal;
}

PolyGraph3D.Model.Face.FindCameraNormal = function(face)
{
    if (face.pathPoints.length < 3)
        return PolyGraph3D.Vector.Z.Copy();
    var edgeA = face.pathPoints[0];
    var origin = face.pathPoints[1];
    var edgeB = face.pathPoints[2];
    var vectorA = PolyGraph3D.Vector.Subtract(edgeA, origin);
    var vectorB = PolyGraph3D.Vector.Subtract(edgeB, origin);
    var normal = PolyGraph3D.Vector.CrossProduct(vectorA, vectorB);
    PolyGraph3D.Vector.Normalize(normal);
    return normal;
}

PolyGraph3D.Model.Face.prototype.set_WorldPoints = function(points)
{
    var dirty = false;
    
    // only keep the points that are being used by this face
    var sub = new Array();
    for (var ptIx = 0; ptIx < this.PointIndices.length; ptIx++)
    {
        sub.push(points[this.PointIndices[ptIx]]);
    }
    
    if (this.pathPoints.length != sub.length)
        dirty = true;
    if (!dirty)
        for (var ix = 0; ix < sub.length; ix++)
            if (this.pathPoints[ix].IsEqual(sub[ix]) == false)
            {
                dirty = true;
                break;
            }
    if (dirty)
    {
        this.dirty = true;
        this.pathPoints = sub;
        this.centerOfGravity = PolyGraph3D.Vector.Average(sub);
    }
}

PolyGraph3D.Model.Face.prototype.set_Distance = function(distance)
{
    if (this.distance != distance)
    {
        this.dirty = true;
        this.distance = distance;
    }
}

PolyGraph3D.Model.Face.prototype.set_Visible = function(visible)
{
    if (this.visible != visible)
    {
        this.dirty = true;
        this.visible = visible;
    }
}

PolyGraph3D.Model.Face.prototype.set_ScreenPoints = function(points)
{
    var dirty = false;
    
    if (this.screenPoints.length != points.length)
        dirty = true;
    if (!dirty)
        for (var ix = 0; ix < points.length; ix++)
            if (this.screenPoints[ix].IsEqual(points[ix]) == false)
            {
                dirty = true;
                break;
            }
    if (dirty)
    {
        this.dirty = true;
        this.screenPoints = points;
    }
}
/// EndRegion: Face



/// Region: Texture
PolyGraph3D.Model.Texture = function(type, value)
{
    this.TextureType = type.toUpperCase();
    this.Value = value;
}
/// EndRegion: Texture



/// Region: Visual
PolyGraph3D.Model.Visual = function()
{
    this.IsContainer = false;
    this._points = new Array();
    this._faces = new Array();
    this._textures = new Array();
    this._parent = null;
    this._location = PolyGraph3D.Vector.Clone(PolyGraph3D.Vector.Zero);
    this._rotation = PolyGraph3D.Vector.Clone(PolyGraph3D.Vector.Zero);
    this._scale = PolyGraph3D.Vector.Clone(PolyGraph3D.Vector.One);
}

PolyGraph3D.Model.Visual.prototype.get_Points = function()
{
    /// <returns type="Array" />
    return this._points;
}

PolyGraph3D.Model.Visual.prototype.set_Points = function(points)
{
    this._points = points;
}

PolyGraph3D.Model.Visual.prototype.get_Location = function()
{
    /// <returns type="PolyGraph3D.Vector" />
    return this._location.Copy();
}

PolyGraph3D.Model.Visual.prototype.set_Location = function(location)
{
    if (!this._location.IsEqual(location))
    {
        this._location = location;
        for (var ix = 0; ix < this._faces.length; ix++)
        {
            this._faces[ix].dirty = true;
        }
    }
}

PolyGraph3D.Model.Visual.prototype.get_Rotation = function()
{
    /// <returns type="PolyGraph3D.Vector" />
    return this._rotation.Copy();
}

PolyGraph3D.Model.Visual.prototype.set_Rotation = function(rotation)
{
    if (!this._rotation.IsEqual(rotation))
    {
        this._rotation = rotation;
        for (var ix = 0; ix < this._faces.length; ix++)
        {
            this._faces[ix].dirty = true;
        }
    }
}

PolyGraph3D.Model.Visual.prototype.get_Scale = function()
{
    /// <returns type="PolyGraph3D.Vector" />
    return this._scale.Copy();
}

PolyGraph3D.Model.Visual.prototype.set_Scale = function(scale)
{
    if (!this._scale.IsEqual(scale))
    {
        this._scale = scale;
        for (var ix = 0; ix < this._faces.length; ix++)
        {
            this._faces[ix].dirty = true;
        }
    }
}

PolyGraph3D.Model.Visual.prototype.AddFace = function(face)
{
    face.OwnerVisual = this;
    this._faces.push(face);
}

PolyGraph3D.Model.Visual.prototype.CreateFace = function(pointIndices, tex)
{
    this.AddFace(new PolyGraph3D.Model.Face(pointIndices, tex));
}

PolyGraph3D.Model.Visual.prototype.RemoveFace = function(face)
{
    face.ownerVisual = null;
    this._faces.remove(face);
}

PolyGraph3D.Model.Visual.prototype.DefineColorTexture = function(colorValue)
{
    var tex = new PolyGraph3D.Model.Texture("Color", colorValue);
    return this._textures.push(tex) - 1;
}

PolyGraph3D.Model.Visual.prototype.DefineImageTexture = function(imageSource)
{
    var tex = new PolyGraph3D.Model.Texture("Image", imageSource);
    return this._textures.push(tex) - 1;
}

PolyGraph3D.Model.Visual.prototype.GetTexture = function(index)
{
    /// <returns type="PolyGraph3D.Model.Texture" />
    return this._textures[index];
}
/// EndRegion: Visual



/// Region: VisualGroup
PolyGraph3D.Model.VisualGroup = function()
{
    this.IsContainer = true;
    this._visuals = new Array();
    this._parent = null;
    this._location = PolyGraph3D.Vector.Clone(PolyGraph3D.Vector.Zero);
    this._rotation = PolyGraph3D.Vector.Clone(PolyGraph3D.Vector.Zero);
    this._scale = PolyGraph3D.Vector.Clone(PolyGraph3D.Vector.One);
}

PolyGraph3D.Model.VisualGroup.prototype.AddVisual = function(visual)
{
    this._visuals.push(visual);
    visual._parent = this;
}

PolyGraph3D.Model.VisualGroup.prototype.RemoveVisual = function(visual)
{
    visual._parent = null;
    this._visuals.remove(visual);
}

PolyGraph3D.Model.VisualGroup.prototype.get_Location = function()
{
    /// <returns type="PolyGraph3D.Vector" />
    return this._location.Copy();
}

PolyGraph3D.Model.VisualGroup.prototype.set_Location = function(location)
{
    if (!this._location.IsEqual(location))
    {
        this._location = location;
        for (var ix = 0; ix < this._faces.length; ix++)
        {
            this._faces[ix].dirty = true;
        }
    }
}

PolyGraph3D.Model.VisualGroup.prototype.get_Rotation = function()
{
    /// <returns type="PolyGraph3D.Vector" />
    return this._rotation.Copy();
}

PolyGraph3D.Model.VisualGroup.prototype.set_Rotation = function(rotation)
{
    if (!this._rotation.IsEqual(rotation))
    {
        this._rotation = rotation;
        for (var ix = 0; ix < this._faces.length; ix++)
        {
            this._faces[ix].dirty = true;
        }
    }
}

PolyGraph3D.Model.VisualGroup.prototype.get_Scale = function()
{
    /// <returns type="PolyGraph3D.Vector" />
    return this._scale.Copy();
}

PolyGraph3D.Model.VisualGroup.prototype.set_Scale = function(scale)
{
    if (!this._scale.IsEqual(scale))
    {
        this._scale = scale;
        for (var ix = 0; ix < this._faces.length; ix++)
        {
            this._faces[ix].dirty = true;
        }
    }
}
/// EndRegion: VisualGroup
